#include<iostream>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
//对信号进行更深入的学习：有关信号的保存和信号的阻塞

//知识普及：每个进程的task_struct 中都内置了信号的位图，发送信号给进程本质就是把对应进程task_struct 中的信号的位图的对应的bit位置1
//这也简介说明了信号是由操作系统来控制的，因为只有它才有资格修改进程task_struct 的内部属性

//信号的一些概念：信号的执行过程称为信号递达
//信号从产生到递达之间的状态称为未决Pending
//进程可以选择阻塞Block某个信号

//被阻塞的信号在产生后将保持在未决的状态(该信号位置一直会置1)，也就是被保存起来了，直到进程解除对该信号的阻塞，才能执行递达
//注意：阻塞和忽略：阻塞是信号保持在未决态，但是忽略是已经递达的信号所执行的行为是忽略，也就是什么都不做

//信号的阻塞和保存本质是因为每个进程都有对应的block位图,pending位图，和对应的函数指针数组
//block位图中置1就是对该位置对应的信号进行阻塞，pending位图中置1就是有信号产生了，此时是未决的
//信号产生后，会先看block中对应的位置是否被阻塞，如果阻塞就将该信号保持置1在pending位图中，如果没有阻塞就将pending中的位置先置0，然后调用对应的信号行为

//打印信号未决位图：
void PrintPending(sigset_t &pending)//sigset_t 是一种信号集（本质是位图），对于这种数据类型的变量我们不能直接操作它的值，应该使用系统接口去改变
{
    for(int signo=31;signo>=1;signo--)//由于0号信号是不存在的，所以我们直接从1号信号开始判断
    {
        if(sigismember(&pending,signo))//sigismember函数用来判断一个信号是否是该信号集的成员，信号集中对应的bit位为1就是该信号集的成员
        {               //如果signo号信号是未决信号集中的成员就会返回1
            cout<<"1";
        }
        else {cout<<"0";}
    }
    cout<<endl;
}
void handler(int signo)
{
    cout<<"catch a signo :"<<signo<<endl;
}


//学习一下关于信号集的各种函数接口：
void test1()
{
    sigset_t bset,oset;//创建两个信号集

    //sigemptyset(sigset_t *p)函数可以对信号集清空，也就是位图全部置0
    sigemptyset(&bset);
    sigemptyset(&oset);
    /*
    for(int i=0;i<=31;i++)//这里把1-31号信号的位置都置1，为了测试是不是所有的信号都会被屏蔽：当然不可能，例如9,19号信号是不会被屏蔽的
    {
        sigaddset(&bset,i);//sigaddset函数把对信号在信号集对应的位置置1
    }
    */
    
    sigprocmask(SIG_SETMASK,&bset,&oset);//sigprocmask函数用来读取或者更改进程的信号屏蔽字也就是blocking阻塞信号集
    //第一个参数有3个选项：1，SIG_BLOCK 只添加我们新增的信号到屏蔽集中 也就是：mask=mask|myset
    //2，SIG_UNBLOCK 只取消我们传入的信号集中的对应位置的屏蔽： mask=mask&~myset
    //3，SIG_SETMASK 直接设置我们的信号集作为新的屏蔽信号集 ： mask=myset

    //这里的第二个参数是我们输入的信号集，第三个参数是将原来的阻塞信号集输出到oset中保存（保存起来是为了以后的恢复）
    
    sigset_t pending;//创建信号集用来保存未决信号集、
    
    int n =sigpending(&pending);//sigpending函数，会输出未决信号集到我们传入的信号集中

    //我们可以自己实现打印信号集函数就可以将信号集中1-31号新号对应的信号集打印出来：
    PrintPending(pending);
}

//利用上述学习到的接口实现对2号信号的屏蔽：
void test2()
{
    signal(2,handler);
    sigset_t bset,oset;//虽然是系统内置的数据类型，但是还是在栈区上开辟的
    //先清空：
    sigemptyset(&bset);
    sigemptyset(&oset);

    //把对应2号信号位置置1：
    sigaddset(&bset,2);//此时bset位信号集中2号信号对应的位置的bit位已经置1

    //再把信号集输入到内核中：
    sigprocmask(SIG_SETMASK,&bset,&oset);//此时我们已经屏蔽成功2号信号

    sigset_t pending;//用来保存未决信号集

    int ct =0;
    while(true)//当我们给进程发2号信号的时候，会发现2号信号一直都处于未决状态，就说明阻塞成功了
    {
        int n =sigpending(&pending);//获取未决信号集
        //打印未决信号集：
        PrintPending(pending);
        sleep(1);
        ct++;
        if(ct==20)//在20秒后就解除阻塞，此时信号会直接递达，就会直接执行信号对应的动作
        {
            cout<<"unlock 2 signo"<<endl;
            sigprocmask(SIG_SETMASK,&oset,nullptr);//恢复默认的信号集到阻塞信号集上，不需要输出旧信号集就置为nullpter
        }
    }


}

int main()
{
    test2();

    return 0;
}